/**
 * @file drv_pwm_dma.c
 * @author Jomb (jomb.liu@solinftec.com)
 * @brief
 * @version 0.1
 * @date 2022-08-09
 *
 * @copyright Copyright (c) 2022
 *
 */
#include <board.h>

#if defined(RT_USING_PWM_DMA) && defined(RT_USING_PWM)
#include "drv_config.h"
//#define DRV_DEBUG
#define LOG_TAG "drv.pwm.dma"
#include <drv_log.h>

enum
{
#ifdef BSP_TIM3_CH1_USING_DMA //
    PWM3_DMA_INDEX,
#endif
    /// XXX TODO: Other timer dma settings
};

enum
{
#ifdef BSP_TIM3_CH1_USING_DMA
    PWM3_CH1_INDEX,
#endif
};

struct stm32_pwm_ch_dma
{
    char *name;
    char *parent_name;
    DMA_HandleTypeDef tim_dma_handle;
    struct rt_device_pwm_dma pwm_dma_device;
    IRQn_Type irq;
    char channel;
    rt_uint16_t * mem; // dma 数据空间
};

static struct stm32_pwm_ch_dma stm32_pwm_ch_dma_obj[] =
    {

#ifdef BSP_TIM3_CH1_USING_DMA
        PWM3_DMA_CONFIG,
#endif
};

static rt_err_t drv_pwm_dma_write(struct rt_device_pwm_dma *device, const void *data, uint16_t size);
static rt_err_t drv_pwm_dma_control(struct rt_device_pwm_dma *device, int cmd, void *arg);
static const struct rt_pwm_dma_ops drv_ops =
    {
        drv_pwm_dma_write,
        drv_pwm_dma_control};


#ifdef BSP_TIM3_CH1_USING_DMA

void TIM3_CH1_DMA_IRQHandler(void)
{
    HAL_DMA_IRQHandler(&(stm32_pwm_ch_dma_obj[PWM3_CH1_INDEX].tim_dma_handle));
}

#endif

static rt_err_t drv_pwm_dma_write(struct rt_device_pwm_dma *device, const void *data, uint16_t size)
{
    rt_err_t result = RT_EOK;
    TIM_HandleTypeDef *htim = RT_NULL;
    rt_device_t pwm_dev;
    char channel = 0;
    rt_uint64_t tim_clock, psc ,period;

    rt_uint32_t * pulse_in = (rt_uint32_t *) data;

    RT_ASSERT(device != RT_NULL);
    RT_ASSERT(((struct stm32_pwm_ch_dma*)(device->parent.user_data))->mem != RT_NULL);
    
    rt_uint16_t * pulse_out = ((struct stm32_pwm_ch_dma*)(device->parent.user_data))->mem;

    pwm_dev = rt_device_find(((struct stm32_pwm_ch_dma *)(device->parent.user_data))->parent_name);

    if (pwm_dev == RT_NULL)
    {
        result = -RT_ERROR;
        return result;
    }

    htim = (TIM_HandleTypeDef *)(pwm_dev->user_data);
    channel = ((struct stm32_pwm_ch_dma *)(device->parent.user_data))->channel;

#if defined(SOC_SERIES_STM32F2) || defined(SOC_SERIES_STM32F4) || defined(SOC_SERIES_STM32F7)
    if (htim->Instance == TIM9 || htim->Instance == TIM10 || htim->Instance == TIM11)
#elif defined(SOC_SERIES_STM32L4)
    if (htim->Instance == TIM15 || htim->Instance == TIM16 || htim->Instance == TIM17)
#elif defined(SOC_SERIES_STM32F1) || defined(SOC_SERIES_STM32F0) || defined(SOC_SERIES_STM32G0)
    if (0)
#endif
    {
#if !defined(SOC_SERIES_STM32F0) && !defined(SOC_SERIES_STM32G0)
        tim_clock = HAL_RCC_GetPCLK2Freq() * 2;
#endif
    }
    else
    {
#if defined(SOC_SERIES_STM32L4) || defined(SOC_SERIES_STM32F0) || defined(SOC_SERIES_STM32G0)
        tim_clock = HAL_RCC_GetPCLK1Freq();
#else
        tim_clock = HAL_RCC_GetPCLK1Freq() * 2;
#endif
    }
    
    tim_clock /= 1000000UL;

    psc = htim->Init.Prescaler + 1;
    period = htim->Init.Period + 1;

    for (int i = 0; i < size ; i++)
    {
        pulse_out[i] = (unsigned long long)pulse_in[i] * tim_clock / psc / 1000ULL;
        if (pulse_out[i] > period)
        {
             pulse_out[i] =  period;
        }
    }

    HAL_TIM_PWM_Start_DMA(htim, channel,(rt_uint32_t *)pulse_out, size);

    return result;
}

static rt_err_t drv_pwm_dma_control(struct rt_device_pwm_dma *device, int cmd, void *arg)
{
    rt_err_t result = RT_EOK;

    struct rt_pwm_dma_configuration *configuration = (struct rt_pwm_dma_configuration *)arg;

    struct rt_device_pwm *pwm_dev;      
    char channel = 0;
    char polar = 0;
    
    RT_ASSERT(configuration->mem != RT_NULL);
    
    ((struct stm32_pwm_ch_dma*)(device->parent.user_data))->mem = configuration->mem;

    pwm_dev = (struct rt_device_pwm *)rt_device_find(((struct stm32_pwm_ch_dma *)(device->parent.user_data))->parent_name);

    if (pwm_dev == RT_NULL)
    {
        result = -RT_ERROR;
        return result;
    }

    channel = ((struct stm32_pwm_ch_dma *)(device->parent.user_data))->channel;
    


    switch (cmd)
    {
    case PWM_DMA_CMD_SET:
        polar = configuration->polarity;
        __HAL_TIM_SET_CAPTUREPOLARITY((TIM_HandleTypeDef *)pwm_dev->parent.user_data, channel, polar );
        return rt_pwm_set(pwm_dev, channel, configuration->period, configuration->pulse);
    
    default:
        return RT_EINVAL;
    }
}

static rt_err_t stm32_hw_pwm_dma_init(struct stm32_pwm_ch_dma *device)
{
    rt_err_t result = RT_EOK;
    rt_device_t pwm_dev;

    rt_uint8_t channel_dma_id = 0;

    RT_ASSERT(device != RT_NULL);

    if (rt_strcmp(device->parent_name, "pwm1") != 0 && rt_strcmp(device->parent_name, "pwm8") != 0)
    {
        /* DMA controller clock enable */
        __HAL_RCC_DMA1_CLK_ENABLE();
    }
    else
    {
        /* DMA controller clock enable */
        __HAL_RCC_DMA2_CLK_ENABLE();
    }

    /* DMA interrupt init */
    HAL_NVIC_SetPriority(device->irq, 0, 0);
    HAL_NVIC_EnableIRQ(device->irq);

    // find the parent class of pwm dma device
    pwm_dev = rt_device_find(device->parent_name);

    if (pwm_dev == RT_NULL)
    {
        result = -RT_ERROR;
        return result;
    }
    // get the parent class point
    device->pwm_dma_device.parent_pwm = (struct rt_device_pwm *)pwm_dev;

    device->tim_dma_handle.Init.Direction = DMA_MEMORY_TO_PERIPH;
    device->tim_dma_handle.Init.PeriphInc = DMA_PINC_DISABLE;
    device->tim_dma_handle.Init.MemInc = DMA_MINC_ENABLE;
    device->tim_dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    device->tim_dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    device->tim_dma_handle.Init.Mode = DMA_NORMAL;
    device->tim_dma_handle.Init.Priority = DMA_PRIORITY_VERY_HIGH;
    device->tim_dma_handle.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    if (HAL_DMA_Init(&device->tim_dma_handle) != HAL_OK)
    {
        //   Error_Handler();
        result = -RT_ERROR;
        return result;
    }
    /* Several peripheral DMA handle pointers point to the same DMA handle.
     Be aware that there is only one stream to perform all the requested DMAs. */
    switch (device->channel)
    {
    case TIM_CHANNEL_1:
        channel_dma_id = TIM_DMA_ID_CC1;
        break;
    case TIM_CHANNEL_2:
        channel_dma_id = TIM_DMA_ID_CC2;
        break;
    case TIM_CHANNEL_3:
        channel_dma_id = TIM_DMA_ID_CC3;
        break;
    case TIM_CHANNEL_4:
        channel_dma_id = TIM_DMA_ID_CC4;
        break;
    default:
        break;
    }

    __HAL_LINKDMA((TIM_HandleTypeDef *)(pwm_dev->user_data), hdma[channel_dma_id], device->tim_dma_handle);

    return result;
}

static int stm32_pwm_dma_init(void)
{
    int i = 0;
    int result = RT_EOK;

    for (i = 0; i < sizeof(stm32_pwm_ch_dma_obj) / sizeof(stm32_pwm_ch_dma_obj[0]); i++)
    {
        if (stm32_hw_pwm_dma_init(&stm32_pwm_ch_dma_obj[i]) != RT_EOK)
        {
            result = -RT_ERROR;
            goto __exit;
        }
        else
        {
            if (rt_device_pwm_dma_register(&stm32_pwm_ch_dma_obj[i].pwm_dma_device,
                                           stm32_pwm_ch_dma_obj[i].name,
                                           &drv_ops,
                                           &stm32_pwm_ch_dma_obj[i]) == RT_EOK)
            {
                result = RT_EOK;
            }
            else
            {
                result = -RT_ERROR;
                goto __exit;
            }
        }
    }
__exit:
    return result;
}
INIT_COMPONENT_EXPORT(stm32_pwm_dma_init); // Depends on pwm_dev init

void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
    char channel = 0;
    switch (htim->Channel)
    {
    case HAL_TIM_ACTIVE_CHANNEL_1:
        channel = TIM_CHANNEL_1;
        break;
    case HAL_TIM_ACTIVE_CHANNEL_2:
        channel = TIM_CHANNEL_2;
        break;
    case HAL_TIM_ACTIVE_CHANNEL_3:
        channel = TIM_CHANNEL_3;
        break;
    case HAL_TIM_ACTIVE_CHANNEL_4:
        channel = TIM_CHANNEL_4;
        break;
    default:
        break;
    }
    HAL_TIM_PWM_Stop_DMA(htim, channel);
}

#endif
